<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    
    <title>WebSocket | 王鹏的技术博客</title>
    
    
        <meta property="og:site_name" content="王鹏的技术博客">
    
    
        <meta property="article:author" content="王 鹏">
    
    
        <link rel="icon" href="/mhexo/img/favicon.ico">
    
    
<link rel="stylesheet" href="/mhexo/css/minireset.min.css">

    
<link rel="stylesheet" href="/mhexo/css/all.min.css">

    
<link rel="stylesheet" href="/mhexo/css/csshake.min.css">

    
<link rel="stylesheet" href="/mhexo/css/hljs/lioshi.css">

    
<link rel="stylesheet" href="/mhexo/css/jquery.fancybox.min.css">

    
<link rel="stylesheet" href="/mhexo/styl/main.css">

    
<script src="/mhexo/js/jquery.min.js"></script>

    
<script src="/mhexo/js/highlight.min.js"></script>

    
<script src="/mhexo/js/jquery.fancybox.min.js"></script>

    
<script src="/mhexo/js/clipboard.min.js"></script>

<meta name="generator" content="Hexo 5.2.0"></head>
<body>
    <header>
  <div class="outer">
    <div class="inner">
      <h1 class="logo-wrap">
        <a>王鹏的技术博客<b><sup>1.1</sup></b></a>
      </h1>
    </div>
    <div class="inner">
      <nav class="main-nav">
        
          <a href="/mhexo/">首页</a>
        
          <a href="/mhexo/archives">归档</a>
        
          <a href="/mhexo/categories">分类</a>
        
          <a href="/mhexo/tags">标签</a>
        
          <a href="/mhexo/friends">友链</a>
        
          <a href="/mhexo/about">关于</a>
        
          <a target="_blank" rel="noopener" href="https://www.baidu.com">百度</a>
        
      </nav>
    </div>
  </div>
</header>
    <div class="content">
        <section class="outer">
    <article>
        <div class="article-title">
    <h2>
        <a href="/mhexo/2020/12/22/WebSocket/" class="shake shake-little" title="WebSocket">
            
            WebSocket
        </a>
    </h2>
    <div class="meta_box">
    
        
        
            
                
        
        <div class="meta meta_auth">
            <img src="/mhexo/img/default.png" alt="head" />
            <p>anonymous</p>
        </div>
    
        
        
        <div class="meta meta_date">
            <i class="fas fa-calendar-alt fa-fw" aria-hidden="true"></i>
            <p>发布于：2020年12月22日</p>
        </div>
    
        <div class="meta meta_update">
            <i class="fas fa-edit fa-fw" aria-hidden="true"></i>
            <p>更新于：2020年12月22日</p>
        </div>
    </div>
    

</div>

        <hr>
        <div class="article-entry">
            
            
            
            <h1 id="WebSocket"><a href="#WebSocket" class="headerlink" title="WebSocket"></a>WebSocket</h1><p>webSocket是一项可以让服务器将数据主动推送给客户端的技术。前几天写了一个日志功能，日志数据需要实时更新。正好项目中有封装好的WebSocket组件，且接口支持webSocket，就用它实现了。也是第一次用，简单研究了一下，分享出来。</p>
<p>文章示例代码：<a href="https://link.zhihu.com/?target=https://github.com/neroneroffy/webSocketDemo">https://github.com/neroneroffy/webSocketDemo</a></p>
<h2 id="什么是WebSocket"><a href="#什么是WebSocket" class="headerlink" title="什么是WebSocket"></a><strong>什么是WebSocket</strong></h2><p>首先需要明白webSocket的概念，下边是维基百科的解释</p>
<blockquote>
<p>  WebSocket是一种通信协议，可在单个TCP连接上进行全双工通信。WebSocket使得客户端和服务器之间的数据交换变得更加简单，允许服务端主动向客户端推送数据。在WebSocket API中，浏览器和服务器只需要完成一次握手，两者之间就可以建立持久性的连接，并进行双向数据传输。</p>
</blockquote>
<p>首先，要明白WebSocket是一种通信协议，区别于HTTP协议，HTTP协议只能实现客户端请求，服务端响应的这种单项通信。<br>而WebSocket可以实现客户端与服务端的双向通讯，说白了，最大也是最明显的区别就是可以做到服务端主动将消息推送给客户端。</p>
<p>其余的特点有：</p>
<ul>
<li>握手阶段采用 HTTP 协议。</li>
<li>数据格式轻量，性能开销小。客户端与服务端进行数据交换时，服务端到客户端的数据包头只有2到10字节，客户端到服务端需要加上另外4字节的掩码。HTTP每次都需要携带完整头部。</li>
<li>更好的二进制支持，可以发送文本，和二进制数据</li>
<li>没有同源限制，客户端可以与任意服务器通信</li>
<li>协议标识符是ws（如果加密，则是wss），请求的地址就是后端支持websocket的API。</li>
</ul>
<h2 id="几种与服务端实时通信的方法"><a href="#几种与服务端实时通信的方法" class="headerlink" title="几种与服务端实时通信的方法"></a><strong>几种与服务端实时通信的方法</strong></h2><p>我们都知道，不使用WebSocket与服务器实时交互，一般有两种方法。AJAX轮询和Long Polling长轮询。</p>
<h3 id="AJAX轮询"><a href="#AJAX轮询" class="headerlink" title="AJAX轮询"></a><strong>AJAX轮询</strong></h3><p>AJAX轮询也就是定时发送请求，也就是普通的客户端与服务端通信过程，只不过是无限循环发送，这样，可以保证服务端一旦有最新消息，就可以被客户端获取。</p>
<h3 id="Long-Polling长轮询"><a href="#Long-Polling长轮询" class="headerlink" title="Long Polling长轮询"></a><strong>Long Polling长轮询</strong></h3><p>Long Polling长轮询是客户端和浏览器保持一个长连接，等服务端有消息返回，断开。<br>然后再重新连接，也是个循环的过程，无穷尽也。。。</p>
<p>客户端发起一个Long Polling，服务端如果没有数据要返回的话，<br>会hold住请求，等到有数据，就会返回给客户端。客户端又会再次发起一次Long Polling，再重复一次上面的过程。</p>
<h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a><strong>缺点</strong></h3><p>上边这两种方式都有个致命的弱点，开销太大，被动性。假设并发很高的话，这对服务端是个考验。<br>而WebSocket一次握手，持久连接，以及主动推送的特点可以解决上边的问题，又不至于损耗性能。</p>
<h2 id="WebSocket连接过程"><a href="#WebSocket连接过程" class="headerlink" title="WebSocket连接过程"></a><strong>WebSocket连接过程</strong></h2><p>客户端发起HTTP握手，告诉服务端进行WebSocket协议通讯，并告知WebSocket协议版本。服务端确认协议版本，升级为WebSocket协议。之后如果有数据需要推送，会主动推送给客户端。</p>
<p>连接开始时，客户端使用HTTP协议和服务端升级协议，升级完成后，后续数据交换遵循WebSocket协议。我们看看Request Headers</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Accept-Encoding: gzip, deflate, br</span><br><span class="line">Accept-Language: zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6</span><br><span class="line">Cache-Control: no-cache</span><br><span class="line">Connection: Upgrade</span><br><span class="line">Host: 127.0.0.1:3000</span><br><span class="line">Origin: http://localhost:3000</span><br><span class="line">Pragma: no-cache</span><br><span class="line">Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits</span><br><span class="line">Sec-WebSocket-Key: bwb9SFiJONXhQ/A4pLaXIg==</span><br><span class="line">Sec-WebSocket-Version: 13</span><br><span class="line">Upgrade: websocket</span><br></pre></td></tr></table></figure>

<p>重点字段是这些：</p>
<ul>
<li>Connection: Upgrade 表示要升级协议</li>
<li>Upgrade: websocket 要升级协议到websocket协议</li>
<li>Sec-WebSocket-Version 表示websocket的版本。如果服务端不支持该版本，需要返回一个Sec-WebSocket-Versionheader，里面包含服务端支持的版本号。</li>
<li>Sec-WebSocket-Key 对应服务端响应头的Sec-WebSocket-Accept，由于没有同源限制，websocket客户端可任意连接支持websocket的服务。这个就相当于一个钥匙一把锁，避免多余的，无意义的连接。</li>
</ul>
<p>再看看看服务端响应的 Response Headers</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Connection: Upgrade</span><br><span class="line">Sec-WebSocket-Accept: 2jrbCWSCPlzPtxarlGTp4Y8XD20=</span><br><span class="line">Upgrade: websocket</span><br></pre></td></tr></table></figure>

<p>关键是这个字段</p>
<ul>
<li>Sec-WebSocket-Accept: 用来告知服务器愿意发起一个websocket连接， 值根据客户端请求头的Sec-WebSocket-Key计算出来</li>
</ul>
<h2 id="WebSocket-API"><a href="#WebSocket-API" class="headerlink" title="WebSocket API"></a><strong>WebSocket API</strong></h2><p>客户端若想要与支持webScoket的服务器通信，可以使用WebSocket构造函数返回WebSocket对象。</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">const ws = new WebSocket(&quot;ws://localhost:3000/websocket&quot;);</span><br></pre></td></tr></table></figure>

<p>这样，客户端就会与服务端开始连接。</p>
<p>返回的实例对象的属性：</p>
<ul>
<li>WebSocket.onopen： 连接成功后的回调</li>
<li>WebSocket.onclose： 连接关闭后的回调</li>
<li>WebSocket.onerror： 连接失败后的回调</li>
<li>WebSocket.onmessage： 客户端接收到服务端数据的回调</li>
<li>webSocket.bufferedAmount： 未发送至服务器的二进制字节数</li>
<li>WebSocket.binaryType： 使用二进制的数据类型连接</li>
<li>WebSocket.protocol ： 服务器选择的下属协议</li>
<li>WebSocket.url ： WebSocket 的绝对路径</li>
<li>WebSocket.readyState： 当前连接状态，对应的四个常量</li>
</ul>
<p>​     WebSocket.CONNECTING: 0</p>
<p>​     WebSocket.OPEN: 1</p>
<p>​     WebSocket.CLOSING: 2</p>
<p>​     WebSocket.CLOSED: 3</p>
<p>方法：</p>
<ul>
<li>WebSocket.close() 关闭当前连接</li>
<li>WebSocket.send(data) 向服务器发送数据</li>
</ul>

        </div>
        <div class="article-copyright">
            
    <blockquote>
        <p>
            版权声明：本文为「子曰无衣」的原创文章，博客内容遵循 署名-非商业性使用-相同方式共享 协议。<br>本文永久链接是：https://wangpdizhi.gitee.io/mhexo/2020/12/22/WebSocket/
        </p>
    </blockquote>


        </div>
    </article>
    

    <section id="toc-div" >
        <ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#WebSocket"><span class="toc-text">WebSocket</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AFWebSocket"><span class="toc-text">什么是WebSocket</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%87%A0%E7%A7%8D%E4%B8%8E%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%AE%9E%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E6%96%B9%E6%B3%95"><span class="toc-text">几种与服务端实时通信的方法</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#AJAX%E8%BD%AE%E8%AF%A2"><span class="toc-text">AJAX轮询</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Long-Polling%E9%95%BF%E8%BD%AE%E8%AF%A2"><span class="toc-text">Long Polling长轮询</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BC%BA%E7%82%B9"><span class="toc-text">缺点</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#WebSocket%E8%BF%9E%E6%8E%A5%E8%BF%87%E7%A8%8B"><span class="toc-text">WebSocket连接过程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#WebSocket-API"><span class="toc-text">WebSocket API</span></a></li></ol></li></ol>
    </section>
    <section id="gohome" style="display: none;"><a>顶</a></section>

    <script>
        function get_top_by_link(link){
            var hnid = "#" + $(link).attr("data");
            if ($(hnid).length > 0){
                return $(hnid).offset().top;
            }else{
                return 0;
            }
        }
        //go to hn
        function gotohn(link){
            $("html,body").animate({scrollTop: get_top_by_link(link) }, 300);
        }
        //页面滚动
        function update(){
            var scrollH = $(window).scrollTop();
            if($(".toc-link")){
                $(".toc-link").each(function(i,link){
                    var mdHeight = get_top_by_link(link);
                    if(mdHeight <= scrollH + 40){
                        //高亮导航菜单
                        $('.toc-link').removeClass('on');
                        $(link).addClass('on');
                    }
                });
            }
            //返回顶部显隐
            if(scrollH < 200){
                $("#gohome").css("display","none");
            }else{
                $("#gohome").css("display","block");
            }
        }
        $(function(){
            //修复部分锚点从属关系
            if($("#toc-div >li").length > 0){
                $("#toc-div >li").appendTo($("#toc-div >ol:first"));
            }
            //返回顶部
            $('#gohome').click(function(){
                $("html,body").animate({scrollTop: 0}, 300);
                return false;
            })
            //遍历锚点
            $(".toc-link").each(function(i,link){
                $(link).attr("data",$(link).attr('href').substring(1));
                $(link).attr("href","javascript:void(0);");
                $(link).attr("onclick","gotohn(this);");
            })
            //绑定滚动事件
            $(window).bind('scroll', update);
            //初始化toc
            var first_toc = $(".toc-link")[0];
            var first_scroll = get_top_by_link(first_toc);
            var window_scroll = $(window).scrollTop();
            if(window_scroll <= first_scroll){
                $(first_toc).addClass('on');
            }
        })
    </script>

</section>
    </div>
    <footer>
    <div class="outer">
        <div class="inner">
            Powered by <a href="http://prowiki.demopage.icu/" target="_blank">ProWiki</a>
            &copy;2020 子曰无衣<br>
            <a target="_blank" rel="noopener" href="http://beian.miit.gov.cn/">冀ICP备00000000号</a>
        </div>
    </div>
</footer>

<script src="/mhexo/js/custom.js"></script>


    <script>onload_content();</script>

</body>
</html>
